#include "LCommon.h"

void reverse_array(ui8* pointer, size_t count)
{
	size_t x;
	ui8 * temp = (ui8*)malloc(count);
	memcpy(temp, pointer, count);
	for(x = 0; x < count; ++x)
		pointer[x] = temp[count-x-1];
	free(temp);
}

AuthSocket::~AuthSocket()
{
}

void AuthSocket::Loop()
{
	while(ThreadState)
	{
		m_readlock.Acquire();
		if(!m_readbuffer.size())
		{
			m_readlock.Release();
			Sleep(5);
			continue;
		}
		RealmPacket pack = **m_readbuffer.begin();
		m_readbuffer.erase(m_readbuffer.begin());
		m_readlock.Release();
		ui8 opcode;
		opcode = *pack.Contents();
		PacketHandler* hdl = &hndl[opcode];
		if(hdl && pack.Contents() && hdl->method)
			(this->*hdl->method)(pack);
	}
}

AuthSocket::AuthSocket()
{
	hndl = new PacketHandler[MAX_LOPS];
	memset(hndl, NULL, MAX_LOPS * sizeof(PacketHandler));
	hndl[CMSG_CHALLENGE].method = &AuthSocket::HandleChallenge;
	hndl[CMSG_PROOF].method = &AuthSocket::HandleProof;
	hndl[CMSG_LISTREALMS].method = &AuthSocket::HandleRealmList;
	N.SetHexStr("894B645E89E1535BBDAD5B8B290650530801B18EBFBF5E8FAB3C82872A3E9BB7");
	g.SetDword(7);
	s.SetRand(256);
}

void AuthSocket::HandleChallenge(RealmPacket& pk)
{
	char* buffer = (char*)pk.Contents();
	memcpy(&m_challenge.cmd, buffer, sizeof(ui8));
	memcpy(&m_challenge.error, (buffer + 1), sizeof(ui8));
	memcpy(&m_challenge.size, (buffer + 2), sizeof(ui16));
	memcpy(m_challenge.gamename, (buffer + 4), 4 * sizeof(ui8));
	memcpy(&m_challenge.version1, (buffer + 8), sizeof(ui8));
	memcpy(&m_challenge.version2, (buffer + 9), sizeof(ui8));
	memcpy(&m_challenge.version3, (buffer + 10), sizeof(ui8));
	memcpy(&m_challenge.build, (buffer + 11), sizeof(ui16));
	memcpy(m_challenge.platform, (buffer + 13), 4 * sizeof(ui8));
	memcpy(m_challenge.os, (buffer + 17), 4 * sizeof(ui8));
	memcpy(m_challenge.country, (buffer + 21), 4 * sizeof(ui8));
	memcpy(&m_challenge.timezone_bias, (buffer + 25), 4 * sizeof(ui8));
	memcpy(&m_challenge.ip, (buffer + 29), 4 * sizeof(ui8));
	memcpy(&m_challenge.I_len, (buffer + 33), sizeof(ui8));
	memcpy(m_challenge.I, (buffer + 34), pk.GetSize() - 34);

	ScopedAllocator<char> accname(m_challenge.I_len + 1);
	memcpy(accname, m_challenge.I, m_challenge.I_len);
	accname[m_challenge.I_len] = 0;

	m_account = AccountMgr->GetAccount(accname);
	if(!m_account)
	{
		Log->Notice("AuthSocket", "Could not find Account %s", accname);
		return;
	}
	const char* encp = (char*)m_account->passwordhash;

	m_account->country = "";
	m_account->country.push_back(m_challenge.country[3]);
	m_account->country.push_back(m_challenge.country[2]);
	m_account->country.push_back(m_challenge.country[1]);
	m_account->country.push_back(m_challenge.country[0]);
	
	ui8 SrpHash[20];
	BigNumber bn;
	bn.SetHexStr(encp);
	memcpy(SrpHash, bn.AsByteArray(), bn.GetNumBytes());
	for(int n = bn.GetNumBytes(); n <= 19; n++)
		SrpHash[n] = (ui8)0;
	reverse_array(SrpHash, 20);
	Sha1Hash sha;
	Log->Notice("Sha", "Begin");
	sha.UpdateData( s.AsByteArray(), 32 );
	sha.UpdateData( SrpHash, 20 );
	Log->Notice("Sha", "End");
	sha.Finalize();

	BigNumber x;
	x.SetBinary( sha.GetDigest(), sha.GetLength() );
	v = g.ModExp(x, N);
	b.SetRand(152);

	BigNumber gmod = g.ModExp(b, N);
	B = ((v * 3) + gmod) % N;

	BigNumber unk;
	unk.SetRand(128);

	ui8 response[200];
	ui32 c = 0;
	response[c] = 0;										c += 1;
	response[c] = 0;										c += 1;
	response[c] = 0;										c += 1;
	memcpy(&response[c], B.AsByteArray(), 32);				c += 32;
	response[c] = 1;										c += 1;
	response[c] = g.AsByteArray()[0];						c += 1;
	response[c] = 32;										c += 1;
	memcpy(&response[c], N.AsByteArray(), 32);				c += 32;
	memcpy(&response[c], s.AsByteArray(), s.GetNumBytes()); c += s.GetNumBytes();
	memcpy(&response[c], unk.AsByteArray(), 16);			c += 16;
	response[c] = 0;										c += 1;

	send(m_socket, (char*)response, c, 0);
}

void AuthSocket::HandleProof(RealmPacket& data)
{
	sAuthLogonProof_C lp;
	ui8* ptr = data.Contents();
	memcpy(&lp.cmd, ptr, 1);
	memcpy(lp.A, ptr + 1, 32);
	memcpy(lp.M1, ptr + 33, 20);
	memcpy(lp.crc_hash, ptr + 53, 20);
	memcpy(&lp.number_of_keys, ptr + 73, 1);
	memcpy(&lp.unk, ptr + 74, 1);

	if(!m_account)
		return;

	BigNumber A;
	A.SetBinary(lp.A, 32);

	Sha1Hash sha;
	sha.UpdateBigNumbers(&A, &B, 0);
	sha.Finalize();

	BigNumber u;
	u.SetBinary(sha.GetDigest(), 20);
	
	BigNumber S = (A * (v.ModExp(u, N))).ModExp(b, N);
	ui8 t[32];
	ui8 t1[16];
	ui8 vK[40];
	memcpy(t, S.AsByteArray(), 32);
	for (int i = 0; i < 16; i++)
	{
		t1[i] = t[i*2];
	}
	sha.Initialize();
	sha.UpdateData(t1, 16);
	sha.Finalize();
	for (int i = 0; i < 20; i++)
	{
		vK[i*2] = sha.GetDigest()[i];
	}
	for (int i = 0; i < 16; i++)
	{
		t1[i] = t[i*2+1];
	}
	sha.Initialize();
	sha.UpdateData(t1, 16);
	sha.Finalize();
	for (int i = 0; i < 20; i++)
	{
		vK[i*2+1] = sha.GetDigest()[i];
	}
	m_sessionkey.SetBinary(vK, 40);

	ui8 hash[20];

	sha.Initialize();
	sha.UpdateBigNumbers(&N, NULL);
	sha.Finalize();
	memcpy(hash, sha.GetDigest(), 20);
	sha.Initialize();
	sha.UpdateBigNumbers(&g, NULL);
	sha.Finalize();
	for (int i = 0; i < 20; i++)
	{
		hash[i] ^= sha.GetDigest()[i];
	}
	BigNumber t3;
	t3.SetBinary(hash, 20);

	const char* u_ptr = m_account->username;

	sha.Initialize();

	sha.UpdateData((const unsigned char*)u_ptr, strlen(u_ptr));
	sha.Finalize();

	BigNumber t4;
	t4.SetBinary(sha.GetDigest(), 20);

	sha.Initialize();
	sha.UpdateBigNumbers(&t3, &t4, &s, &A, &B, &m_sessionkey, NULL);
	sha.Finalize();

	BigNumber M;
	M.SetBinary(sha.GetDigest(), 20);

	if(memcmp(lp.M1, M.AsByteArray(), 20) != 0)
	{
		SendCError(4);
		return;
	}

	memcpy(m_account->sessionkey, m_sessionkey.AsByteArray(), 40);

	sha.Initialize();
	sha.UpdateBigNumbers(&A, &M, &m_sessionkey, 0);
	sha.Finalize();

	SendPError(0, sha.GetDigest());
	m_authenticated = true;
}

void AuthSocket::SendPError(ui8 err, ui8* M)
{
	ui8 buffer[32];
	memset(buffer, 0, 32);

	buffer[0] = 1;
	buffer[1] = err;
	if(M == 0)
	{
		*(ui32*)&buffer[2] = 3;
		send(m_socket, (char*)buffer, 6, 0);
		return;
	}

	memcpy(&buffer[2], M, 20);
	send(m_socket, (char*)buffer, 32, 0);
}

void AuthSocket::SendCError(ui8 err)
{
	ui8 buffer[3];
	buffer[0] = buffer[1] = 0;
	buffer[2] = err;

	send(m_socket, (char*)buffer, 3, 0);
}

void AuthSocket::HandleRealmList(RealmPacket& pk)
{
	RealmPacket pack;
	ui16 size = sRealmHandler->m_realms.size();
	pack << ui8(0x10) << ui16(0x00) << ui32(0x00);
	pack << size;
	set<Realm*>::iterator itr = sRealmHandler->m_realms.begin();
	set<Realm*>::iterator end = sRealmHandler->m_realms.end();
	for( ; itr != end; ++itr)
	{
		pack << (*itr)->icon << ui8(0) << (*itr)->color;
		pack << (*itr)->name << (*itr)->addr;
		pack << float(0.0);
		map<ui32, ui8>::iterator it = (*itr)->mapping.find(m_account->id);
		ui8 num = it == (*itr)->mapping.end() ? 0 : it->second;
		pack << num << ui8(9) << (*itr)->id;
	}
	pack << ui8(0x17) << ui8(0);
	*((ui16*)((ui8*)pack.Contents() + 1)) = pack.GetSize() - 3;

	send(m_socket, (char*)pack.Contents(), pack.GetSize(), 0);
}

void AuthSocket::HandleReconnectChallenge(RealmPacket& pack)
{
	HandleChallenge(pack);
}